﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OnlineScheduling.DomainModel
{
    public class NPWSPTRandomizedSolver : Solver
    {
        public NPWSPTRandomizedSolver(Instance instance) : base(instance)
        {
            random=new Random((int)DateTime.Now.Ticks/1024);
        }

        protected Random random;

        public override int Run()
        {
            int weightedSum = 0;
            int time = 0;
            receivedTasksByPrioritySorted = new Heap<Task>();
            currentyWorkingCPUs = new Heap<Processor>();
            int accumulatedSumOfDurations = 0;
            int tasksInQueCounter = 0;

            while (!Instance.IsEmpty || currentyWorkingCPUs.Count > 0 ||
                   receivedTasksByPrioritySorted.Count > 0)
            {
                //wybranie pierwszego zdarzenie - albo przybycia nowego zadania, albo zakończenie

                int firstFinishTime = -1;
                if (currentyWorkingCPUs.Count > 0)
                    firstFinishTime = currentyWorkingCPUs.Root.CurrentTask.ExpectedTimeToBeDone;
                // jeśli są jakieś zadanka robione i jest szansa że któreś się zakończy

                int firstNewComeIn = Instance.GetTimeOfNextTask();
                //czas nowego zadanka, to jest w kopcu, w którym priorytetem jest arrival, więc optymalnie :)


                //wyznaczenie co jest pierwszym zadaniem

                int eventId = -1;
                int minimum = -1;

                if (firstFinishTime != -1)
                {
                    minimum = firstFinishTime;
                    eventId = 1;
                }
                if (firstNewComeIn != -1 && (firstNewComeIn < minimum || minimum == -1))
                {
                    minimum = firstNewComeIn;
                    eventId = 2;
                }


                switch (eventId)
                {
                    case 1:
                        time = firstFinishTime;
                        while (currentyWorkingCPUs.Count > 0 && currentyWorkingCPUs.Root.CurrentTask.ExpectedTimeToBeDone == time)
                        {
                            Processor freed = currentyWorkingCPUs.Pop();
                            Task finished = freed.StopProcessingTask(time);
                            weightedSum += finished.Weight*finished.TimeFinished;
                            idListOfFreeCPUs.Add(freed.Id);
                        }
                        break;

                    case 2:
                        time = firstNewComeIn;

                        //odbierz na raz wszystkie z tego czasu, mogą się pojawić jednocześnie....
                        while (Instance.GetTimeOfNextTask() == time)
                        {
                            Task toPut = Instance.GetNextTask();
                            toPut.PriorityType = TasksPriorityType.ByWeightDurationDivision;
                            receivedTasksByPrioritySorted.Push(toPut);
                            accumulatedSumOfDurations += toPut.Duration;
                            tasksInQueCounter++;
                        }


                        break;
                }

                //przydzial na wolne procesory

                while (idListOfFreeCPUs.Count > 0 && receivedTasksByPrioritySorted.Count > 0)
                {
                    //jesli jest duzo prockow (wiecej niz jeden) to na luzie radosnie przydzielamy
                    if (idListOfFreeCPUs.Count>1) AssignTaskOnFirstFreeProcessor(time);
                    else //ojj malo prockow wolnych, nie szastamy naszym ostatnim
                    {
                        //policz srednia - zeby potem ocenic czy zadanko dluzsze czy krotsze
                        double average = accumulatedSumOfDurations/tasksInQueCounter;
                        Task candidate = receivedTasksByPrioritySorted.Root;
                            double probabilityOfAssign = average/candidate.Duration;
                               
                                double randomNo = random.NextDouble();

                                //jak mial szczescie to przydziel albo juz byl raz odlozony, jak nie to odloz
                                if (randomNo <= probabilityOfAssign || candidate.WasDelayed==true) AssignTaskOnFirstFreeProcessor(time);
                                else
                                {
                                    candidate.WasDelayed = true;
                                    break;//nalezy wyjsc z petli
                                } 
                    }
                }
            }

            WeightedSum = weightedSum;
            return WeightedSum;
        }

        protected void AssignTaskOnFirstFreeProcessor(int time)
        {
            int idOfCPU = idListOfFreeCPUs.First();
            idListOfFreeCPUs.Remove(idListOfFreeCPUs.First());
            Task toAssign = receivedTasksByPrioritySorted.Pop();
            processors[idOfCPU].AssignTask(toAssign, time);
            currentyWorkingCPUs.Push(processors[idOfCPU]);
        }
    }
}
